﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Reflection;
using PSE.ProcessManager.Common.Gateway;
using PSE.ProcessManager.Server.InteligenceModules;
using PSE.ProcessManager.Server.Process;
using System.Threading;
using PSE.ProcessManager.Entity;
using PSE.ProcessManager.Entity.Enums;
using PSE.ProcessManager.ResourceAccess;
using System.Linq;
using PSE.ProcessManager.Server.Process.Configuration;

namespace PSE.ProcessManager.Server.InteligenceManagerInstance
{
    /// <summary>
    /// Classe responsável em carregar os InteligenceModules.
    /// </summary>
    internal class InteligenceModuleLoader : IDisposable
    {
        #region Properties

        private static Dictionary<int, BaseInteligenceModule> _singleCachedInteligenceModules = new Dictionary<int, BaseInteligenceModule>();
        private static Dictionary<MultipleProcessKey, BaseInteligenceModule> _multipleCachedInteligenceModules = new Dictionary<MultipleProcessKey, BaseInteligenceModule>(new MultipleProcessKeyComparer());
        private const string _inteligenceModuleFolderPathKey = "InteligenceModuleFolderPath";
        private static string _inteligenceModuleFolderPath;
        private static Dictionary<int, AutoResetEvent> _processSemaphore = new Dictionary<int, AutoResetEvent>();

        private IProcessManagerResourceAccess _resourceAccess;
        #endregion

        /// <summary>
        /// Construtor
        /// </summary>
        public InteligenceModuleLoader()
        {
            try
            {
                _inteligenceModuleFolderPath = System.Configuration.ConfigurationManager.AppSettings[_inteligenceModuleFolderPathKey];
                _resourceAccess = ProcessManagerResourceAccessFactory.CreateProvider();
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException("Could not find InteligenceModuleFolderPath in configuration file. See inner exception for more details.", ex);
            }


            if (String.IsNullOrEmpty(_inteligenceModuleFolderPath))
                throw new ConfigurationErrorsException("Could not find InteligenceModuleFolderPath in configuration file");
        }

        /// <summary>
        /// Carrega o InteligenceModule de acordo com as configurações recebidas.
        /// </summary>
        /// <param name="inteligenceModuleConfiguration">configurações do InteligenceModule</param>
        /// <returns></returns>
        private BaseInteligenceModule LoadInteligenceModuleFromAssembly(InteligenceModuleEntity inteligenceModuleConfiguration)
        {
            Assembly assembly = null;
            Type inteligenceModuleType = null;
            object inteligenceModuleObject = null;
            string inteligenceModuleFullPath = "";

            try
            {
                inteligenceModuleFullPath = RetrieveInteligenceModuleFullPath(inteligenceModuleConfiguration);

                //Carrega o Assembly do Processo
                assembly = Assembly.LoadFrom(inteligenceModuleFullPath);
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException(String.Format("Could not load InteligenceModule assembly \"{0}\". See inner exception for more details.", inteligenceModuleFullPath), ex);
            }

            string inteligenceModuleClass = "";

            try
            {
                // Obtém o tipo da Janela
                inteligenceModuleClass = inteligenceModuleConfiguration.Assembly + "." + inteligenceModuleConfiguration.Class;

                inteligenceModuleType = assembly.GetType(inteligenceModuleClass);

                if (inteligenceModuleType == null)
                {
                    throw new ConfigurationErrorsException(String.Format("The provided class name \"{0}\" could not be found in InteligenceModule assembly \"{1}\". ", inteligenceModuleClass, inteligenceModuleFullPath));
                }
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException(String.Format("The provided class name \"{0}\" could not be found in InteligenceModule assembly \"{1}\". See inner exception for more details.", inteligenceModuleClass, inteligenceModuleFullPath), ex);
            }

            try
            {
                //Inicializa uma nova instância do processo.  
                inteligenceModuleObject = Activator.CreateInstance(inteligenceModuleType);
            }
            catch (Exception ex)
            {
                throw new Exception("Could not create a instance of InteligenceModule. See inner exception for more details.", ex);
            }

            BaseInteligenceModule inteligenceModule = inteligenceModuleObject as BaseInteligenceModule;

            if (inteligenceModule == null)
            {
                throw new Exception("The InteligenceModule provided does not inherit from BaseInteligenceModule class.");
            }

            return inteligenceModule;
        }

        /// <summary>
        /// Recupera o caminho completo do assembly do Inteligence Module.
        /// </summary>
        /// <param name="inteligenceModuleConfiguration">Configurações do InteligenceModule</param>
        /// <returns>caminho completo do assembly.</returns>
        private string RetrieveInteligenceModuleFullPath(InteligenceModuleEntity inteligenceModuleConfiguration)
        {
            string inteligenceModulePath = _inteligenceModuleFolderPath;

            if (!inteligenceModulePath.EndsWith(@"\"))
                inteligenceModulePath += @"\";

            inteligenceModulePath += inteligenceModuleConfiguration.Assembly;

            if (!inteligenceModulePath.ToLower().EndsWith(".dll"))
                inteligenceModulePath += ".dll";

            return inteligenceModulePath;
        }

        /// <summary>
        /// Carrega e inicializa o InteligenceModule associado a mensagem.
        /// </summary>
        /// <param name="message">Mensagem</param>
        /// <returns>Instância do InteligenceModule</returns>
        internal BaseInteligenceModule LoadAndInitializeInteligenceModule(int processId, string clientIdentifier)
        {
            BaseInteligenceModule inteligenceModule = null;
            ProcessEntity processConfiguration = null;
            InteligenceModuleEntity inteligenceModuleConfiguration = null;

            try
            {
                //Retorna as informações referentes ao Processo e ao InteligenceModule associado.
                _resourceAccess.RetrieveInteligenceModuleAndProcessConfiguration(processId, out processConfiguration, out inteligenceModuleConfiguration);
            }
            catch (Exception ex)
            {
                throw new Exception("Could not retrieve InteligenceModule or Process configurations. See inner exception for more details.", ex);
            }

            try
            {
                if (processConfiguration.InstanceContext == InstanceContext.Single)
                {
                    AutoResetEvent semaphore = null;
                    lock (_processSemaphore)
                    {
                        if (!_processSemaphore.ContainsKey(processId))
                            _processSemaphore.Add(processId, new AutoResetEvent(false));
                        else
                            semaphore = _processSemaphore[processId];
                    }

                    if (semaphore != null)
                        semaphore.WaitOne();

                    inteligenceModule = RetrieveInteligenceModuleFromSingleCache(processConfiguration.Id.GetValueOrDefault());

                    if (inteligenceModule != null &&
                        !inteligenceModule.IsEnabled)
                    {
                        inteligenceModule = null;
                        goto EXIT;
                    }

                    if (inteligenceModule == null)
                    {
                        //Carreaga o InteligenceModule
                        inteligenceModule = LoadInteligenceModuleFromAssembly(inteligenceModuleConfiguration);
                        inteligenceModule.ProcessConfiguration = processConfiguration;

                        if (!inteligenceModule.IsEnabled)
                        {
                            inteligenceModule = null;
                            goto EXIT;
                        }

                        //Adiciona no cache.
                        lock (_singleCachedInteligenceModules)
                        {
                            _singleCachedInteligenceModules.Add(processConfiguration.Id.GetValueOrDefault(), inteligenceModule);
                        }

                        inteligenceModule.ProcessExited += new EventHandler(inteligenceModule_ProcessExited);
                    }
                }
                else
                {
                    MultipleProcessKey key = new MultipleProcessKey();
                    key.ProcessId = processId;
                    key.ClientId = clientIdentifier;

                    inteligenceModule = RetrieveInteligenceModuleFromMultipleCache(key);

                    if (inteligenceModule != null &&
                        !inteligenceModule.IsEnabled)
                    {
                        inteligenceModule = null;
                        goto EXIT;
                    }

                    if (inteligenceModule == null)
                    {
                        //Carrega o InteligenceModule
                        inteligenceModule = LoadInteligenceModuleFromAssembly(inteligenceModuleConfiguration);
                        inteligenceModule.ProcessConfiguration = processConfiguration;

                        if (!inteligenceModule.IsEnabled)
                        {
                            inteligenceModule = null;
                            goto EXIT;
                        }

                        lock (_multipleCachedInteligenceModules)
                        {
                            _multipleCachedInteligenceModules.Add(key, inteligenceModule);
                        }
                        inteligenceModule.ProcessExited += new EventHandler(inteligenceModule_ProcessExited);
                    }
                }
            }
            catch (Exception ex)
            {
                lock (_processSemaphore)
                {
                    if (_processSemaphore.ContainsKey(processId))
                        _processSemaphore.Remove(processId);
                }

                throw new Exception("Could not load InteligenceModule. See inner exception for more details.", ex);
            }

            //Inicializa o Processo.
            if (inteligenceModule.IsProcessRunning == -1)
            {
                try
                {
                    inteligenceModule.StartProcess();
                    inteligenceModule.InitializeProcess(clientIdentifier);
                }
                catch
                {
                    lock (_processSemaphore)
                    {
                        if (_processSemaphore.ContainsKey(processId))
                            _processSemaphore.Remove(processId);
                    }

                    throw;
                }
            }

        EXIT:

            if (processConfiguration.InstanceContext == InstanceContext.Single)
            {
                lock (_processSemaphore)
                {
                    if (_processSemaphore.ContainsKey(processId))
                    {
                        lock (_processSemaphore[processId])
                        {
                            //Libera o processo.
                            _processSemaphore[processId].Set();
                        }
                        //Remove o processo do semáforo.
                        _processSemaphore.Remove(processId);
                    }
                }
            }

            return inteligenceModule;
        }

        void inteligenceModule_ProcessExited(object sender, EventArgs e)
        {
            BaseInteligenceModule module = (BaseInteligenceModule)sender;

            lock (_singleCachedInteligenceModules)
            {
                if (_singleCachedInteligenceModules.ContainsValue(module))
                {
                    _singleCachedInteligenceModules.Remove(_singleCachedInteligenceModules.Single(m => m.Value == module).Key);
                }
            }

            lock (_multipleCachedInteligenceModules)
            {
                if (_multipleCachedInteligenceModules.ContainsValue(module))
                {
                    _multipleCachedInteligenceModules.Remove(_multipleCachedInteligenceModules.Single(m => m.Value == module).Key);
                }
            }

            module.ProcessExited -= new EventHandler(inteligenceModule_ProcessExited);
        }

        /// <summary>
        /// Procura e, caso exista, retorna o InteligenceModule do Cache.
        /// </summary>
        /// <param name="processId">Id do Processo.</param>
        /// <returns></returns>
        internal BaseInteligenceModule RetrieveInteligenceModuleFromSingleCache(int processId)
        {
            BaseInteligenceModule inteligenceModule = null;

            lock (_singleCachedInteligenceModules)
            {
                if (_singleCachedInteligenceModules.ContainsKey(processId))
                    inteligenceModule = _singleCachedInteligenceModules[processId];
            }

            return inteligenceModule;
        }

        /// <summary>
        /// Procura e, caso exista, retorna o InteligenceModule do Cache.
        /// </summary>
        /// <param name="processId">Id do Processo.</param>
        /// <returns></returns>
        internal BaseInteligenceModule RetrieveInteligenceModuleFromMultipleCache(MultipleProcessKey key)
        {
            BaseInteligenceModule inteligenceModule = null;

            lock (_multipleCachedInteligenceModules)
            {
                if (_multipleCachedInteligenceModules.ContainsKey(key))
                    inteligenceModule = _multipleCachedInteligenceModules[key];
            }

            return inteligenceModule;



        }

        /// <summary>
        /// Remove do cache de InteligenceModules, o inteligenceModule associado com o id do processo.
        /// </summary>
        /// <param name="processId">Id do processo.</param>
        internal void RemoveInteligenceModuleFromCache(int processId, string clientIdentifier)
        {
            lock (_singleCachedInteligenceModules)
            {
                if (_singleCachedInteligenceModules.ContainsKey(processId))
                {
                    _singleCachedInteligenceModules.Remove(processId);
                }
            }

            MultipleProcessKey key = new MultipleProcessKey();
            key.ProcessId = processId;
            key.ClientId = clientIdentifier;

            lock (_multipleCachedInteligenceModules)
            {
                if (_multipleCachedInteligenceModules.ContainsKey(key))
                {
                    _multipleCachedInteligenceModules.Remove(key);
                }
            }

        }

        /// <summary>
        /// Verifica as configurações de recuperação do processo em caso de falhas.
        /// </summary>
        internal void HandleProcessOnFail(int processId)
        {
            lock (_singleCachedInteligenceModules)
            {
                if (_singleCachedInteligenceModules.ContainsKey(processId))
                {
                    BaseInteligenceModule im = _singleCachedInteligenceModules[processId];
                }
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            lock (_singleCachedInteligenceModules)
            {
                foreach (KeyValuePair<int, BaseInteligenceModule> value in _singleCachedInteligenceModules)
                {
                    value.Value.Dispose();
                }
            }

            lock (_multipleCachedInteligenceModules)
            {
                foreach (KeyValuePair<MultipleProcessKey, BaseInteligenceModule> value in _multipleCachedInteligenceModules)
                {
                    value.Value.Dispose();
                }
            }
        }

        #endregion

        internal BaseInteligenceModule RetrieveInteligenceModuleFromMultipleCache(int processId)
        {
            lock (_multipleCachedInteligenceModules)
            {
                foreach (KeyValuePair<MultipleProcessKey, BaseInteligenceModule> inteligenceModule in _multipleCachedInteligenceModules)
                {
                    if (inteligenceModule.Key.ProcessId == processId)
                        return inteligenceModule.Value;
                }
            }
            return null;
        }
    }
}
